package com.rxyb.toolize.http;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.IdleConnectionEvictor;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 改造版的HTTPCLIENT
 * 
 * @coder Penn,@auth Penn
 * @date 2017年7月20日
 */
@Slf4j
public class HttpProtocolHandler {

//    private static final Logger                log                                  = LoggerFactory.getLogger(HttpProtocolHandler.class);

    private static String                      DEFAULT_CHARSET                     = "UTF-8";

    /** 连接超时时间，由bean factory设置，缺省为8秒钟 */
    private int                                defaultConnectionTimeout            = 60000;

    /** 回应超时时间, 由bean factory设置，缺省为30秒钟 */
    private int                                defaultSoTimeout                    = 60000;

    /** 闲置连接超时时间, 由bean factory设置，缺省为60秒钟 */
    private int                                defaultIdleConnTimeout              = 60000;

    private int                                defaultMaxConnPerHost               = 30;

    private int                                defaultMaxTotalConn                 = 80;

    /** 默认等待HttpConnectionManager返回连接超时（只有在达到最大连接数时起作用）：1秒 */
    private static final int                   defaultHttpConnectionManagerTimeout = 3 * 1000;

    /**
     * HTTP连接管理器，该连接管理器必须是线程安全的.
     */
    private PoolingHttpClientConnectionManager connectionManager;
    /** 沟通 */
    private CloseableHttpClient                httpClient;
    /** 4.4才发布，但是管用啊 */
    private IdleConnectionEvictor              monitor;
    /** 加载 */
    private static AtomicBoolean               isLoad                              = new AtomicBoolean(false);
    /**
     * 实例
     */
    private static HttpProtocolHandler         httpProtocolHandler                 = new HttpProtocolHandler();

    /**
     * 工厂方法
     * 
     * @return
     */
	public static HttpProtocolHandler getInstance() {
        return httpProtocolHandler;
    }

    /**
     * 信任管理器
     */
    @SuppressWarnings("unused")
    private static X509TrustManager tm = new X509TrustManager() {
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    };

    /**
     * 私有的构造方法
     */
    private HttpProtocolHandler() {
        if (isLoad.compareAndSet(false, true)) {
            loadConfig();
        }
    }

    /**
     * NoSuchAlgorithmException
     * KeyManagementException
     */
    private void loadConfig() {

        // SSL配置
       /* SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[] { tm }, null);
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
            sslContext = SSLContexts.createSystemDefault();
        }*/

        // SSL套接字连接工厂,NoopHostnameVerifier为信任所有服务器
       /* SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", sslsf).build();*/

        // 连接配置
        ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Charset.forName("utf-8")).build();
        // 创建一个线程安全的HTTP连接池
        connectionManager = new PoolingHttpClientConnectionManager();//socketFactoryRegistry
        connectionManager.setMaxTotal(defaultMaxTotalConn); // 池中最大连接数
        connectionManager.setDefaultMaxPerRoute(defaultMaxConnPerHost);// 每个路由从池中掠夺的最大连接数
        connectionManager.setValidateAfterInactivity(1000);// 对静止连接进行验证时间间隔
        connectionManager.setDefaultConnectionConfig(connectionConfig);

        RequestConfig config = RequestConfig.custom().setConnectTimeout(defaultConnectionTimeout)// 连接超时
                .setSocketTimeout(defaultSoTimeout) // 读超时时间（等待数据超时）
                .setConnectionRequestTimeout(defaultHttpConnectionManagerTimeout)// 从池中获取连接超时时间
                .build();

        httpClient = HttpClients.custom().setConnectionManager(connectionManager)// 连接管理器
                .setDefaultRequestConfig(config) // 默认请求配置
                .build();

        monitor = new IdleConnectionEvictor(connectionManager, defaultIdleConnTimeout, TimeUnit.MILLISECONDS);// 源码里这个实现很简单粗暴 ，但是 管用啊
        monitor.start();

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            @Override
            public void run() {
                log.info("帮助GC销毁HttpClient连接池");
                release();
            }
        }));
    }

    /**
     * 释放资源，后面再说从哪用
     * 有用的
     */
    private void release() {
        // 注意调用顺序
        if (null != monitor) {
            monitor.shutdown();
        }
        if (null != httpClient) {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * HTTP-Get
     * @param url
     * @param params a=1&b=2
     * @return
     * @throws IOException 
     * @throws HttpException 
     */
    public HttpResponse doGet(String url,Map<String, String> params,Map<String, String> headers) throws HttpException, IOException {
        HttpRequest request=new HttpRequest(HttpResultType.STRING);
     // 设置编码集
        request.setCharset("utf-8");
        if (null != params) {
            StringBuilder stringBuilder=new StringBuilder("?");
            for(Entry<String,String> entry:params.entrySet()) {
                stringBuilder.append(entry.getKey());
                stringBuilder.append("=");
                stringBuilder.append(entry.getValue());
                stringBuilder.append("&");
            }
            request.setQueryString(stringBuilder.substring(0, stringBuilder.length()-1));
        }
        
        request.setUrl(url);
        request.setHeaders(headers);
        request.setMethod(HttpRequest.METHOD_GET);
        return execute(request);
    }
   
    /**
     * HTTP-Post-FORM
     * @param url 请求地址
     * @param params 参数列表
     * @return
     * @throws IOException 
     * @throws HttpException 
     */
    public HttpResponse doPostWithForm(String url,Map<String, String> params,Map<String, String> headers) throws HttpException, IOException {
        HttpRequest request=new HttpRequest(HttpResultType.STRING);
     // 设置编码集
        request.setCharset("utf-8");
        if (null != params) {
            List<NameValuePair> nameValuePair = new ArrayList<NameValuePair>();
            for(Entry<String,String> entry:params.entrySet()) {
                nameValuePair.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }                         
            
            request.setParameters(nameValuePair);
        }
        request.setHeaders(headers);
        
        request.setUrl(url);
        request.setMethod(HttpRequest.METHOD_POST);
        return execute(request);
    }
    
    /**
     * HTTP-Post-Multi
     * @param url 请求地址
     * @param params 参数列表
     * @return
     * @throws IOException 
     * @throws HttpException 
     */
    public HttpResponse doPostWithMultiform(String url,Map<String, String> params,String fileName,String fileParaName,byte[] file,Map<String, String> headers) throws HttpException, IOException {
        HttpRequest request=new HttpRequest(HttpResultType.STRING);
     // 设置编码集
        request.setCharset("utf-8");
        if (null != params) {
            List<NameValuePair> nameValuePair = new ArrayList<NameValuePair>();
            for(Entry<String,String> entry:params.entrySet()) {
                nameValuePair.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
           
            request.setParameters(nameValuePair);
        }
        request.setFile(file);
        request.setFileName(fileName);
        request.setFileParaName(fileParaName);
        request.setHeaders(headers);
        
        request.setUrl(url);
        request.setMethod(HttpRequest.METHOD_MULTI_POST);
        return execute(request);
    }
    /**
     * HTTP-Post-STREAM
     * @param url 请求地址
     * @param headers 参数列表
     * @return
     * @throws IOException 
     * @throws HttpException 
     */
    public HttpResponse doPostWithStream(String url,String json,Map<String, String> headers) throws HttpException, IOException {
        HttpRequest request=new HttpRequest(HttpResultType.STRING);
     // 设置编码集
        request.setCharset("utf-8");
        request.setQueryString(json);
        
        request.setUrl(url);
        request.setMethod(HttpRequest.METHOD_POST_STREAM);
        request.setHeaders(headers);
        return execute(request);
    }
    
    /**
     * 执行Http请求
     * 
     * @param request 请求数据
     * @return
     * @throws HttpException, IOException
     */
    public HttpResponse execute(HttpRequest request) throws HttpException, IOException {
        if (null == request ) {
            return null;
        }
        
        String charset = request.getCharset();
        charset = charset == null ? DEFAULT_CHARSET : charset;
        HttpRequestBase method = null;

        // 下面这步有质疑，待优化
        RequestConfig config = RequestConfig.custom().setSocketTimeout(defaultSoTimeout) // 读取数据超时
                .setConnectTimeout(defaultConnectionTimeout) // 连接超时
                .setConnectionRequestTimeout(defaultHttpConnectionManagerTimeout) // 池中请求连接
                .setExpectContinueEnabled(false).build();
        // get模式且不带上传文件
        if (request.getMethod().equals(HttpRequest.METHOD_GET)) {
            StringBuffer reqUrl=new StringBuffer(request.getUrl());
            if (StringUtils.isNotBlank(request.getQueryString())) {
//                method = new HttpGet(request.getUrl()+request.getQueryString());
                reqUrl.append(request.getQueryString());
            }
            method = new HttpGet(reqUrl.toString());
//            method.addHeader("Accept", "application/json; charset=" + charset);

            
        } else if (HttpRequest.METHOD_POST.equals(request.getMethod())) {
            method = new HttpPost(request.getUrl());
            
            ((HttpPost) method).setEntity(new UrlEncodedFormEntity(request.getParameters(),"utf-8"));

        } else if (HttpRequest.METHOD_POST_STREAM.equals(request.getMethod())) {
         // post模式且不带上传文件
            method = new HttpPost(request.getUrl());
//            method.addHeader("Content-Type", "application/json; charset=" + charset);
//            method.addHeader("Accept", "application/json; charset=" + charset);
            String paramStr=request.getQueryString();
            System.out.println("请求参数:"+paramStr);
            log.info("请求参数:"+paramStr);
            HttpEntity params = new StringEntity(paramStr, Charset.forName("utf-8"));

            ((HttpPost) method).setEntity(params);
//            method.addHeader(Common.HEADER, Common.HEADER_PREFIX+Common.TOKEN);
        }else {
            // post模式且带上传文件
            method = new HttpPost(request.getUrl());

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setCharset(Charset.forName("utf-8"));
            List<NameValuePair> kvs = request.getParameters();

            for (NameValuePair kv : kvs) {
                builder.addPart(kv.getName(), new StringBody(kv.getValue(), ContentType.TEXT_PLAIN.withCharset("utf-8")));
            }

            // FileBody fb=new FileBody(new File(file64));
            // builder.addPart(strParaFileName, fb);
            if (null != request.getFile() && null != request.getFileName()) {
                ByteArrayBody bab = new ByteArrayBody(request.getFile(), request.getFileName());
                builder.addPart(request.getFileParaName(), bab);
            }
            

            HttpEntity params = builder.build();

            // 设置请求体
            ((HttpPost) method).setEntity(params);
        }

        // 设置Http Header中的User-Agent属性
        method.setConfig(config);
        if (null != request.getHeaders()) {
            for(Entry<String, String> entry:request.getHeaders().entrySet()) {
                method.addHeader(entry.getKey(), entry.getValue());
            }
        }
        
        
        HttpResponse response = new HttpResponse();
        CloseableHttpResponse resp = null;
        try {

            HttpContext context = HttpClientContext.create();
            resp = httpClient.execute(method, context);
            response.setResponseHeaders(resp.getAllHeaders());// 先把头放回去
            if (request.getResultType().equals(HttpResultType.STRING)) {
                response.setStringResult(EntityUtils.toString(resp.getEntity(), charset));
            } else if (request.getResultType().equals(HttpResultType.BYTES)) {
                response.setByteResult(EntityUtils.toByteArray(resp.getEntity()));
            }

        } catch (UnknownHostException ex) {
            log.error("UnknownHostException网络操作异常:",ex);
            ex.printStackTrace();
            return null;
        } catch (IOException ex) {
            log.error("IOException网络操作异常:",ex);
            ex.printStackTrace();
            return null;
        } catch (Exception ex) {
            log.error("Exception网络操作异常:",ex);
            ex.printStackTrace();
            return null;
        } finally {
            if (null != resp) {
                EntityUtils.consume(resp.getEntity());// 释放资源
                // resp.close();//会关唉
            }
            if (null != method) {
                method.releaseConnection();
            }
        }
        return response;
    }

   
}
